#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/stat.h>

#include <global/malloc.h>
#include <global/types.h>
#include <global/const.h>
#include <logger/logger.h>

t_log * glog = NULL;

static inline t_fd log_cutbytime_fd(t_log * plog);
static inline t_fd log_cutbysize_fd(t_log * plog);
static inline t_fd log_rollbytime_fd(t_log * plog);
static inline t_fd log_rollbysize_fd(t_log * plog);

t_log * log_create(const char * ln)
{
	return log_create_cut(ln, None, 0, 0, 0, 0);
}

void log_setglog(t_log * log)
{
	if (glog != NULL)
	{
		log_destroy(glog);
	}
	glog = log;
}

t_log * log_getglog()
{
	return glog;
}

t_log * log_create_cbt(const char * ln, long cutp1)
{
	long cutp;
	cutp1 = cutp1 == 0 ? 60 : cutp1;
	cutp = time(NULL);
	cutp = ((time_t)(cutp / (double)cutp1)) * cutp1;
	return log_create_cut(ln, CutByTime, cutp, cutp1, 0, 0);
}

t_log * log_create_cbs(const char * ln, long cutp)
{
	cutp = cutp == 0 ? 1024 : cutp;
	return log_create_cut(ln, CutBySize, cutp, 0, 0, 0);
}

t_log * log_create_rbt(const char * ln, long cutp1, long cutp2)
{
	long cutp;
	cutp1 = cutp1 == 0 ? 60 : cutp1;
	cutp = time(NULL);
	cutp = ((time_t)(cutp / (double)cutp1)) * cutp1;
	return log_create_cut(ln, RollByTime, cutp, cutp1, cutp2, 0);
}

t_log * log_create_rbs(const char * ln, long cutp, long cutp1)
{
	return log_create_cut(ln, RollBySize, cutp, cutp1, 0, 0);
}

t_log * log_create_cut(const char * ln, t_logcut cut, long cutp, long cutp1, long cutp2, long cutp3)
{
	t_log * plog;
	char fn[MAXFULLFILENAME] = { 0 };
	
	plog = (t_log *)s_malloc(sizeof(t_log));

	if (plog == NULL)
	{
		return NULL;
	}

	strncpy(plog->ln, ln, MAXFULLFILENAME);

	plog->level = LOG_INFO;

	plog->cut = cut;
	plog->cutp = cutp;
	plog->cutp1 = cutp1;
	plog->cutp2 = cutp2;
	plog->cutp3 = cutp3;

	if (strcmp(plog->ln, "/dev/null") == 0)
	{
		plog->fd = -1;
	}
	else
	{
		switch (plog->cut)
		{
			case CutByTime:
				snprintf(fn, MAXFULLFILENAME, "%s_%s.log", plog->ln, dt_sfmt(&plog->cutp, "%Y%m%d%H%M"));
				break;
			case None:
			case RollByTime:
			case RollBySize:
			case CutBySize:
				snprintf(fn, MAXFULLFILENAME, "%s.log", plog->ln);
				break;
		}

		if (access(fn, F_OK | R_OK | W_OK) == 0)
		{
			plog->fd = open(fn, O_RDWR | O_APPEND, 0666);
		}
		else
		{
			plog->fd = open(fn, O_CREAT | O_RDWR | O_APPEND, 0666);
		}

		if (plog->fd == -1)
		{
			fprintf(stderr, "Open log file (%s) failed (%s).\n", plog->ln, strerror(errno));
			free(plog);
			return NULL;
		}
	}

	return plog;
}

void log_destroy(t_log * plog)
{
	if (plog != NULL)
	{
		close(plog->fd);
		free(plog);
		plog = NULL;
	}
}

void log_level(t_log * plog, t_loglevel level)
{
	if (plog != NULL)
	{
		plog->level = level;
	}
}

t_fd log_fd(t_log * plog)
{
	if (plog != NULL)
	{
		return plog->fd;
	}
	return -1;
}

int log_write(t_log * plog, const char * fmt, ...)
{
	va_list ap;
	int tn, ln, sn;
	char nfn[MAXFULLFILENAME] = { 0 };
	char buffer[MAXBUFFER] = { 0 };

	if (plog == NULL)
	{
		return -1;
	}
	
	if (plog->fd == -1)
	{
		return -1;
	}

	tn = snprintf(buffer, MAXBUFFER, "%s ", dt_snow("%Y%m%d%H%M%S"));

	va_start(ap, fmt);
	ln = vsnprintf(buffer + tn, MAXBUFFER, fmt, ap);
	va_end(ap);

	sn = tn + ln;
	if (sn >= MAXBUFFER - 1)
	{
		buffer[MAXBUFFER - 1] = '\0';
		buffer[MAXBUFFER - 2] = '\n';
		sn = MAXBUFFER - 1;
	}
	else
	{
		buffer[sn] = '\n';
	}

	switch (plog->cut)
	{
		case None:
			break;
		case CutByTime:
			log_cutbytime_fd(plog);
			break;
		case CutBySize:
			log_cutbysize_fd(plog);
			break;
		case RollByTime:
			log_rollbytime_fd(plog);
			break;
		case RollBySize:
			log_rollbysize_fd(plog);
			break;
	}

	if (write(plog->fd, buffer, sn + 1) == -1)
	{
		fprintf(stderr, "fail to write log (%s), because %s.", buffer, strerror(errno));
		return -1;
	}

	return 0;
}

static inline t_fd log_cutbytime_fd(t_log * plog)
{
	t_fd fd;
	time_t tn;
	char scut[MAXLINE] = { 0 };
	char fn[MAXFULLFILENAME] = { 0 };

	flock(plog->fd, LOCK_EX);

	if (tn - plog->cutp > plog->cutp1)
	{
		tn = time(NULL);

		fd = plog->fd;

		close(fd);

		plog->fd = -1;

		plog->cutp = ((time_t)(tn / (double)plog->cutp1)) * plog->cutp1;

		snprintf(fn, MAXFULLFILENAME, "%s_%s.log", plog->ln, dt_sfmt(&plog->cutp, "%Y%m%d%H%M"));

		if (access(fn, F_OK | R_OK | W_OK) == 0)
		{
			plog->fd = open(fn, O_RDWR | O_APPEND, 0666);
		}
		else
		{
			plog->fd = open(fn, O_CREAT | O_RDWR | O_APPEND, 0666);
		}

		if (plog->fd == -1)
		{
			switch (plog->cut)
			{
				case None: // never
					strncpy(scut, "t_logcut::None", MAXLINE);
					break;
				case CutByTime:
					strncpy(scut, "t_logcut::CutByTime", MAXLINE);
					break;
				case CutBySize:
					strncpy(scut, "t_logcut::CutBySize", MAXLINE);
					break;
				case RollByTime:
					strncpy(scut, "t_logcut::RollByTime", MAXLINE);
					break;
				case RollBySize:
					strncpy(scut, "t_logcut::RollBySize", MAXLINE);
					break;
			}
			fprintf(stderr, "fail to create a new log file (%s), %s", fn, scut);
		}
	}

	return plog->fd;
}

static inline t_fd log_cutbysize_fd(t_log * plog)
{
	t_fd fd;
	int ret, i;
	struct stat fs;
	char scut[MAXLINE] = { 0 };
	char fn[MAXFULLFILENAME] = { 0 };
	char nfn[MAXFULLFILENAME] = { 0 };

	bzero(&fs, sizeof(struct stat));

	ret = fstat(plog->fd, &fs);

	if (ret == -1)
	{
		fprintf(stderr, "fail to get file (%s.log) status, because %s.", plog->ln, strerror(errno));
		return plog->fd;
	}

	if (fs.st_size >= plog->cutp)
	{
		fd = plog->fd;

		snprintf(fn, MAXFULLFILENAME, "%s.log", plog->ln);

		close(fd);

		plog->fd = -1;
		
		snprintf(nfn, MAXFULLFILENAME, "%s.log.%d", plog->ln, plog->cutp1);
		ret = rename(fn, nfn);
		if (ret == -1)
		{
			fprintf(stderr, "fail on rename (%s) to (%s), because %s.");
			plog->fd = -1;
			return -1;
		}

		plog->fd = open(fn, O_CREAT | O_RDWR | O_APPEND, 0666);

		if (plog->fd == -1)
		{
			fprintf(stderr, "fail to create a new file (%s), because, %s.", fn, strerror(errno));
			return -1;
		}

		++ plog->cutp1;
	}

	return 0;
}

static inline t_fd log_rollbytime_fd(t_log * plog)
{
	t_fd fd;
	time_t tn;
	int ret;
	struct stat fs;
	char fn[MAXFULLFILENAME] = { 0 };
	char nfn[MAXFULLFILENAME] = { 0 };

	bzero(&fs, sizeof(struct stat));

	tn = time(NULL);

	if (tn - plog->cutp > plog->cutp1)
	{
		fd = plog->fd;

		close(fd);

		plog->fd = -1;

		snprintf(fn, MAXFULLFILENAME, "%s.log", plog->ln);

		plog->cutp = ((time_t)(tn / (double)plog->cutp1)) * plog->cutp1;

		snprintf(nfn, MAXFULLFILENAME, "%s.log.%d", plog->ln, plog->cutp3);

		ret = rename(fn, nfn);

		if (ret == -1)
		{
			fprintf(stderr, "fail to rename (%s) to (%s), because %s.", fn, nfn, strerror(errno));
			plog->fd = -1;
			return -1;
		}

		plog->fd = open(fn, O_CREAT | O_RDWR | O_APPEND, 0666);

		if (plog->fd == -1)
		{
			fprintf(stderr, "fail to create a new file (%s), because %s.", plog->ln, strerror(errno));
			return -1;
		}

		if (plog->cutp3 >= plog->cutp2)
		{
			plog->cutp3 = 0;
		}
		else
		{
			++ plog->cutp3;
		}
	}

	return 0;
}

static inline t_fd log_rollbysize_fd(t_log * plog)
{
	t_fd fd;
	int ret;
	struct stat fs;
	char fn[MAXFULLFILENAME] = { 0 };
	char nfn[MAXFULLFILENAME] = { 0 };

	bzero(&fs, sizeof(struct stat));

	ret = fstat(plog->fd, &fs);

	if (ret == -1)
	{
		fprintf(stderr, "fail to get file (%s) status, because %s.", plog->ln, strerror(errno));
		return -1;
	}

	if (fs.st_size >= plog->cutp)
	{
		fd = plog->fd;

		close(fd);

		plog->fd = -1;

		snprintf(fn, MAXFULLFILENAME, "%s.log", plog->ln);

		snprintf(nfn, MAXFULLFILENAME, "%s.log.%d", plog->ln, plog->cutp2);

		ret = rename(fn, nfn);

		if (ret == -1)
		{
			fprintf(stderr, "fail to rename file (%s) to (%s), because %s.", fn, nfn, strerror(errno));
			return -1;
		}

		plog->fd = open(fn, O_CREAT | O_RDWR | O_APPEND, 0666);

		if (plog->fd == -1)
		{
			fprintf(stderr, "fail to create a new file (%s), because %s.", fn, strerror(errno));
			return -1;
		}

		if (plog->cutp1 <= plog->cutp2)
		{
			plog->cutp2 = 0;
		}
		else
		{
			++ plog->cutp2;
		}
	}

	return 0;
}
